package ar.edu.utn.frre.sistop.simumem.logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;

import ar.edu.utn.frre.sistop.simumem.exceptions.NoMemoryException;
import ar.edu.utn.frre.sistop.simumem.exceptions.NoProcessesException;
import ar.edu.utn.frre.sistop.simumem.exceptions.ProcessSizeTooBigException;
import ar.edu.utn.frre.sistop.simumem.model.Algoritmo;
import ar.edu.utn.frre.sistop.simumem.model.Asignacion;
import ar.edu.utn.frre.sistop.simumem.model.Estado;
import ar.edu.utn.frre.sistop.simumem.model.Memoria;
import ar.edu.utn.frre.sistop.simumem.model.Particion;
import ar.edu.utn.frre.sistop.simumem.model.Proceso;
import ar.edu.utn.frre.sistop.simumem.model.Resultado;

public class Simulador {

	private static Resultado sResultado;

	public static Resultado ParticionesFijas(ArrayList<Proceso> procesos, Memoria memoria,
			Algoritmo algoritmo) {

		// inicializacion de variables
		long t = 0;
		ArrayList<Asignacion> asignaciones = new ArrayList<Asignacion>();
		ArrayList<Proceso> listos = new ArrayList<Proceso>();
		ArrayList<Proceso> terminados = new ArrayList<Proceso>();
		ArrayList<Particion> disponibles = new ArrayList<Particion>(memoria.getParticiones());
		sResultado = new Resultado();
		sResultado.setTiemposRetorno(new HashMap<Proceso, Long>(procesos.size()));
		sResultado.setTiemposEspera(new HashMap<Proceso, Long>(procesos.size()));
		boolean terminado = false;

		// mientras no hayamos terminado de simular
		while (!terminado) {
			// restar 1 instante al restante de cada proceso en memoria y
			// reacomodar terminados, disponibles y asignaciones
			Iterator<Asignacion> itAsignaciones = asignaciones.iterator();
			while (itAsignaciones.hasNext()) {
				Asignacion asignacionActual = itAsignaciones.next();
				asignacionActual.setRestante(asignacionActual.getRestante() - 1);
				if (asignacionActual.getRestante() == 0) {
					Proceso proceso = asignacionActual.getProceso();
					sResultado.getTiemposRetorno().put(proceso, t - proceso.getArribo());
					sResultado.getTiemposEspera().put(proceso, t - proceso.getArribo() - proceso.getIrrupcion());
					terminados.add(proceso);
					disponibles.add(asignacionActual.getParticion());
					itAsignaciones.remove();
				}
			}

			// ordenar disponibles de acuerdo a la posicion de inicio
			Collections.sort(disponibles);

			// agregar procesos que arribaron, a la cola de listos
			for (Proceso p : procesos) {
				if (p.getArribo() == t) {
					listos.add(p);
				}
			}

			// asignar procesos en la cola de listos, a particiones (aca esta el dato)
			Iterator<Proceso> itListos = listos.iterator();
			while (itListos.hasNext()) {
				Proceso procesoActual = itListos.next();
				// buscamos una partición adecuada para el proceso, de acuerdo al algoritmo
				Particion elegida = null;
				Iterator<Particion> itDisponibles = disponibles.iterator();
				while (itDisponibles.hasNext()) {
					Particion particionActual = itDisponibles.next();
					// si el proceso entra en la particion
					if (procesoActual.getTamano() <= particionActual.getTamano()) {
						switch (algoritmo) {
						case FirstFit:
							// si no se ha elegido particion
							if (elegida == null) {
								elegida = particionActual;
							}
							break;
						case BestFit:
							// si se ha elegido particiin, pero la nueva es de menor tamano;
							// o si no se ha elegido particion
							if ((elegida != null && particionActual.getTamano() < elegida
									.getTamano()) || elegida == null) {
								elegida = particionActual;
							}
							break;
						case WorstFit:
							// si se ha elegido particion, pero la nueva es de mayor tamano;
							// o si no se ha elegido particion
							if ((elegida != null && particionActual.getTamano() > elegida
									.getTamano()) || elegida == null) {
								elegida = particionActual;
							}
							break;
						}
					}
				}

				// si hemos encontrado una particion adecuada
				if (elegida != null) {
					asignaciones.add(new Asignacion(elegida, procesoActual, procesoActual
							.getIrrupcion()));
					itListos.remove();
					disponibles.remove(elegida);
				}
			}

			// guardar el estado actual
			ArrayList<Asignacion> asignacionesCopia = new ArrayList<Asignacion>();
			Iterator<Asignacion> itAsignaciones2 = asignaciones.iterator();
			while (itAsignaciones2.hasNext()) {
				Asignacion asignacionActual = itAsignaciones2.next();
				Asignacion asignacionCopia = new Asignacion(asignacionActual.getParticion(),
						asignacionActual.getProceso(), asignacionActual.getRestante());
				asignacionesCopia.add(asignacionCopia);
			}
			ArrayList<Proceso> listosCopia = new ArrayList<Proceso>(listos);
			ArrayList<Proceso> terminadosCopia = new ArrayList<Proceso>(terminados);
			sResultado
					.agregarEstado(new Estado(t, asignacionesCopia, listosCopia, terminadosCopia));

			// fijarse si se termino de simular
			if (terminados.size() == procesos.size()) {
				terminado = true;
			} else {
				t++;
			}
		}
		return sResultado;
	}

	public static void validarTamanoDeProcesos(ArrayList<Proceso> procesos, Memoria memoria)
			throws NoMemoryException, NoProcessesException, ProcessSizeTooBigException {
		if (procesos.size() == 0)
			throw new NoProcessesException();
		if (memoria.getParticiones().size() == 0)
			throw new NoMemoryException();
		long tamanoMayorParticion = memoria.getTamanoMayorParticion();
		Iterator<Proceso> itProcesos = procesos.iterator();
		while (itProcesos.hasNext()) {
			Proceso actual = itProcesos.next();
			if (actual.getTamano() > tamanoMayorParticion)
				throw new ProcessSizeTooBigException(actual.getId(), actual.getTamano());
		}
	}

	// public static ArrayList<EstadoVariable>
	// ParticionesVariables(ArrayList<Proceso> procesos, long memoria, Algoritmo
	// algoritmo, boolean compactar) throws ProcessSizeTooBigException {
	// // validar tama�o de procesos
	// Iterator<Proceso> itProcess = procesos.iterator();
	// while (itProcess.hasNext()) {
	// Proceso current = itProcess.next();
	// if (current.getTamano() > memoria) throw new
	// ProcessSizeTooBigException("El proceso " + current.getId() +
	// " de tama�o " + current.getTamano() + " no cabe en la memoria.");
	// }
	// // inicializaci�n
	// long t = 0;
	// ArrayList<Asignacion> asignaciones = new ArrayList<Asignacion>();
	// ArrayList<Proceso> listos = new ArrayList<Proceso>();
	// ArrayList<Proceso> terminados = new ArrayList<Proceso>();
	// Espacio inicial = new Espacio(0, memoria);
	// ArrayList<Espacio> espacios = new ArrayList<Espacio>();
	// espacios.add(inicial);
	// boolean terminado = false;
	// while (!terminado) {
	// // restar 1 instante al restante de cada proceso en memoria y
	// // reacomodar terminados, disponibles y asignaciones
	// Iterator<Asignacion> itAsig = asignaciones.iterator();
	// while (itAsig.hasNext()) {
	// Asignacion a = itAsig.next();
	// a.setRestante(a.getRestante() - 1);
	// if (a.getRestante() == 0) {
	// terminados.add(a.getProceso());
	// espacios.add(new Espacio(a.getParticion().getInicio(),
	// a.getParticion().getTamano()));
	// itAsig.remove();
	// }
	// }
	//
	// // ordenar espacios
	// Collections.sort(espacios, new EspacioComparator());
	//
	// // combinar espacios contiguos
	// if (espacios.size() > 1) {
	// Iterator<Espacio> eit = espacios.iterator();
	// Espacio first = eit.next();
	// ArrayList<Espacio> nuevoEspacios = new ArrayList<Espacio>();
	// nuevoEspacios.add(first);
	// Espacio ultimoNuevo = nuevoEspacios.get(nuevoEspacios.size() - 1);
	// while (eit.hasNext()) {
	// Espacio current = eit.next();
	// if (ultimoNuevo.getInicio() + ultimoNuevo.getTamano() ==
	// current.getInicio()) {
	// ultimoNuevo.setTamano(ultimoNuevo.getTamano() + current.getTamano());
	// } else {
	// nuevoEspacios.add(current);
	// ultimoNuevo = nuevoEspacios.get(nuevoEspacios.size() - 1);
	// }
	// }
	// espacios = nuevoEspacios;
	// }
	//
	// // agregar procesos a listos
	// for (Proceso p : procesos) {
	// if (p.getArribo() == t) {
	// listos.add(p);
	// }
	// }
	//
	// // buscar espacios libres adecuados para asignarlos a procesos
	// Iterator<Proceso> itPro = listos.iterator();
	// while (itPro.hasNext()) {
	// Proceso pro = itPro.next();
	// Espacio elegido = null;
	// Iterator<Espacio> itEsp = espacios.iterator();
	// while (itEsp.hasNext()) {
	// Espacio esp = itEsp.next();
	// // si el proceso entra en el espacio
	// if (pro.getTamano() <= esp.getTamano()) {
	// switch (algoritmo) {
	// case FirstFit:
	// // si no se ha elegido espacio
	// if (elegido == null) {
	// elegido = esp;
	// }
	// break;
	// case BestFit:
	// // si se ha elegido espacio, pero el nuevo es de
	// // menor tama�o,
	// // o si no se ha elegido espacio
	// if ((elegido != null && esp.getTamano() < elegido.getTamano()) || elegido
	// == null) {
	// elegido = esp;
	// }
	// break;
	// case WorstFit:
	// // si se ha elegido espacio, pero el nuevo es de
	// // mayor tama�o,
	// // o si no se ha elegido espacio
	// if ((elegido != null && esp.getTamano() > elegido.getTamano()) || elegido
	// == null) {
	// elegido = esp;
	// }
	// break;
	// }
	// }
	// }
	//
	// // asignar proceso a espacio
	// if (elegido != null) {
	// asignaciones.add(new Asignacion(new Particion((long) 0,
	// elegido.getInicio(), pro.getTamano()), pro, pro.getIrrupcion()));
	// if (pro.getTamano() == elegido.getTamano()) {
	// espacios.remove(elegido);
	// } else {
	// espacios.set(espacios.indexOf(elegido), new Espacio(elegido.getInicio() +
	// pro.getTamano(), elegido.getTamano() - pro.getTamano()));
	// }
	// itPro.remove();
	// }
	// // compactar si corresponde
	// else if (espacios.size() > 1 & compactar) {
	// Iterator<Espacio> itEspa = espacios.iterator();
	// long tamEsp = 0;
	// while (itEspa.hasNext()) {
	// tamEsp += itEspa.next().getTamano();
	// }
	// if (pro.getTamano() <= tamEsp) {
	// // ordenar asignaciones
	// Collections.sort(asignaciones, new AsignacionComparator());
	// // mover asignaciones a la izquierda
	// Iterator<Asignacion> itAs = asignaciones.iterator();
	// long posicion = 0;
	// while (itAs.hasNext()) {
	// Asignacion actual = itAs.next();
	// actual.setParticion(new Particion((long) 0, posicion,
	// actual.getParticion().getTamano()));
	// posicion += actual.getParticion().getTamano();
	// }
	// // Compactar espacios
	// Iterator<Espacio> itEs = espacios.iterator();
	// while (itEs.hasNext()) {
	// itEs.next();
	// itEs.remove();
	// }
	// // ver si el proceso entra en el espacio nuevo
	// asignaciones.add(new Asignacion(new Particion((long) 0, posicion,
	// pro.getTamano()), pro, pro.getIrrupcion()));
	// if (pro.getTamano() < tamEsp) {
	// espacios.add(0, new Espacio(posicion + pro.getTamano(), tamEsp -
	// pro.getTamano()));
	// }
	// itPro.remove();
	// }
	//
	// }
	// }
	//
	// // guardar el estado actual
	// ArrayList<Asignacion> as = new ArrayList<Asignacion>();
	// Iterator<Asignacion> itAs = asignaciones.iterator();
	// while (itAs.hasNext()) {
	// Asignacion a = itAs.next();
	// Asignacion b = new Asignacion(a.getParticion(), a.getProceso(),
	// a.getRestante());
	// as.add(b);
	// }
	// ArrayList<Espacio> es = new ArrayList<Espacio>();
	// Iterator<Espacio> itEs = espacios.iterator();
	// while (itEs.hasNext()) {
	// Espacio a = itEs.next();
	// Espacio b = new Espacio(a.getInicio(), a.getTamano());
	// es.add(b);
	// }
	// ArrayList<Proceso> li = new ArrayList<Proceso>(listos);
	// ArrayList<Proceso> te = new ArrayList<Proceso>(terminados);
	// estadosVariables.add(new EstadoVariable(t, as, es, li, te));
	//
	// // fijarse si se termin� de simular
	// if (terminados.size() == procesos.size()) {
	// terminado = true;
	// } else {
	// t++;
	// }
	// }
	// return estadosVariables;
	// }
}